class Board {

    private cities :{[name :string] :City;};
    private tracks :{[name :string] :{[name :string] :Track[];};};

    constructor() {
        this.cities = {};
        this.tracks = {};
    }

    addCity(city :City) :void {
        if (city === undefined || city === null)
            throw ("city parameter may not be undefined or null.");

        if (this.cities.hasOwnProperty(city.getName()))
            throw ("Board already has city with name (" + city.getName() + ").");

        this.cities[city.getName()] = city;
    }

    containsCityByName(cityName :string) :boolean {
        if (cityName === undefined || cityName === null || cityName === "")
            throw ("cityName parameter may not be undefined or null.");

        return (this.cities.hasOwnProperty(cityName));
    }

    getCityByName(cityName :string) :City {
        if (cityName === undefined || cityName === null || cityName === "")
            throw ("cityName parameter may not be undefined or null.");

        if (this.cities.hasOwnProperty(cityName))
            return this.cities[cityName];
        else
            return undefined;
    }

    addTrack(track :Track) :void {
        if (track === undefined || track === null)
            throw ("track parameter may not be undefined or null.");

        if (!(this.containsCityByName(track.getFrom().getName())))
            throw ("track is from unknown city (" + track.getFrom().getName());
        if (!(this.containsCityByName(track.getTo().getName())))
            throw ("track is to unknown city (" + track.getTo().getName());

        var fromCityName = track.getFrom().getName();
        var toCityName = track.getTo().getName();

        if (!(this.tracks.hasOwnProperty(fromCityName)))
            this.tracks[fromCityName] = {};
        if (!(this.tracks.hasOwnProperty(toCityName)))
            this.tracks[toCityName] = {};

        if (this.tracks[fromCityName].hasOwnProperty(toCityName)) {
            this.tracks[fromCityName][toCityName].push(track);
        } else {
            this.tracks[fromCityName][toCityName] = [track];
        }

        if (this.tracks[toCityName].hasOwnProperty(fromCityName)) {
            this.tracks[toCityName][fromCityName].push(track);
        } else {
            this.tracks[toCityName][fromCityName] = [track];
        }
    }

    containsTrack(track :Track) :boolean {
        if (track === undefined || track === null)
            throw ("track parameter may not be undefined or null.");

        var tracks = this.getTracksBetween(track.getFrom().getName(), track.getTo().getName());
        var trackIndex = 0;
        for(; trackIndex < tracks.length; ++trackIndex) {
            if (tracks[trackIndex] === track)
                return true;
        }

        return false;
    }

    getTracksBetween(fromCityName :string, toCityName :string) :Track[] {
        if (fromCityName === undefined || fromCityName === null || fromCityName === "")
            throw ("fromCityName parameter may not be undefined or null.");
        if (toCityName === undefined || toCityName === null || toCityName === "")
            throw ("toCityName parameter may not be undefined or null.");

        if (this.tracks.hasOwnProperty(fromCityName)) {
            if (this.tracks[fromCityName].hasOwnProperty(toCityName)) {
                return this.tracks[fromCityName][toCityName].slice(0);
            } else {
                return [];
            }
        } else {
            return [];
        }
    }
}